///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_DBUS_CMESSAGE_H_
#define _BUOLA_DBUS_CMESSAGE_H_

#include <buola/dbus.h>
#include <buola/dbus/cobjectbase.h>
#include <buola/utility/uvalue.h>
#include <buola/traits/tuple.h>
#include <map>

namespace buola { namespace dbus {

///\addtogroup dbus
///@{

//used to send object paths as "o"
class CObjectPath
{
public:
    explicit CObjectPath(const std::string &pS)
        :   mV(pS)
    {}

    CObjectPath()
    {}
    
    CObjectPath(const CObjectPath &pO)
        :   mV(pO.mV)
    {}

    const std::string &Get() const  {   return mV;    }

private:
    std::string mV;
};

//used to receive the path a message was sent to
class CPath
{
private:
    explicit CPath(const std::string &pS)
        :   mV(pS)
    {}

public:
    CPath()
    {}
    
    CPath(const CPath &pO)
        :   mV(pO.mV)
    {}

    const std::string &Get() const  {   return mV;    }

    std::string GetRelative(CObjectBase *pO) const
    {
        int lOLen=pO->DBusGetPath().length();

        if(mV.length()-lOLen<=0) return "/";
        return mV.substr(lOLen);
    }

private:
    std::string mV;

    friend void extract_value(CMessage *pM,CPath &pV);
};

class CMember
{
private:
    explicit CMember(const std::string &pS)
        :   mV(pS)
    {}

public:
    CMember(const CMember &pO)
        :   mV(pO.mV)
    {}

    const std::string &Get() const  {   return mV;    }

private:
    std::string mV;

    friend void extract_value(CMessage *pM,CMember &pV);
};

class CInterface
{
private:
    explicit CInterface(const std::string &pS)
        :   mV(pS)
    {}

public:
    CInterface(const CInterface &pO)
        :   mV(pO.mV)
    {}

    const std::string &Get() const  {   return mV;    }

private:
    std::string mV;

    friend void extract_value(CMessage *pM,CInterface &pV);
};

DECLARE_NAME(MethodCall)
DECLARE_NAME(EmptyReply)
DECLARE_NAME(Received)
DECLARE_NAME(NewReply)
DECLARE_NAME(NewError)

////////////////////////////////////////////////////////////////////////////

/// D-BUS message

/// Represents a message received or sent to the message bus.
///
////////////////////////////////////////////////////////////////////////////

class CMessage
{
//! implementation details
//@{
    struct SPrivate;
    struct SIterator;
//@}

//! \name constructors and destructor
//@{
public:
    //! used in CBus to create a received message
    CMessage(NMethodCall,const CProxy &pProxy,const std::string &pInterface,const std::string &pMethod);
    CMessage(NEmptyReply,CBus *pBus,uint32_t pSerial);
    CMessage(NReceived,CBus *pBus,void *pMessage);
    CMessage(NNewReply,CMessage *pReplyTo);
    CMessage(NNewError,CMessage *pReplyTo,const std::string &pName,const std::string &pMsg);
    //! destructor
    virtual ~CMessage();
    
    void FillEmpty(void *pMessage);
//@}

public:
//! \name public interface
//@{
    //! adds an argument to the message (used when sending)
//    template<typename tType>
//    void Append(const tType &pType)

    void AppendVariadic() {}
    template<typename tFirst,typename... tRest>
    void AppendVariadic(const tFirst &pFirst,const tRest&... pRest)
    {
        append_value(this,pFirst);
        AppendVariadic(pRest...);
    }

    template<typename tType>
    void Append(const tType &pV)
    {
        append_value(this,pV);
    }
    
    //! true if there are arguments available to extract (used when receiving)
    char CanExtract();

    //! skips a member while extracting
    void Skip();
    
    //! extracts an argument from the message
    template<typename tType>
    void Extract(tType &pV)
    {
        extract_value(this,pV);
    }

    template<typename tType>
    tType Extract()
    {
        tType lV;
        extract_value(this,lV);
        return lV;
    }
    
    template<typename tType>
    void ExtractVariant(tType &pV)
    {
        OpenContainer();
        extract_value(this,pV);
        CloseContainer();
    }
    
    template<typename tType>
    tType ExtractVariant()
    {
        tType lV;
        OpenContainer();
        extract_value(this,lV);
        CloseContainer();
        return lV;
    }
    
private:
///\todo check when this works in clang

//    template<int N,typename... tArgs>
//    typename std::enable_if<N==sizeof...(tArgs),void>::type
//    ExtractTupleImpl(std::tuple<tArgs...> &pArgs)
//    {
//    }

//    template<int N,typename... tArgs>
//    typename std::enable_if<N<sizeof...(tArgs),void>::type
//    ExtractTupleImpl(std::tuple<tArgs...> &pArgs)
//    {
//        extract_value(this,std::get<N>(pArgs));
//        ExtractTupleImpl<N+1>(pArgs);
//    }

//    template<typename... tArgs>
//    void ExtractTupleImpl(std::tuple<tArgs...> &pArgs,std::integral_constant<size_t,0)
//    {
//    }

//    template<typename... tArgs,size_t N>
//    void ExtractTupleImpl(std::tuple<tArgs...> &pArgs,std::integral_constant<size_t,N>)
//    {
//        extract_value(this,std::get<N>(pArgs));
//        ExtractTupleImpl(pArgs,std::integral_constant<size_t,N-1>);
//    }

    template<typename... tArgs>
    void ExtractTupleImpl(std::tuple<tArgs...>& /*pArgs*/,const MIndexTuple<>&)
    {
    }

    template<typename... tArgs,std::size_t vIndex,std::size_t... vIndices>
    void ExtractTupleImpl(std::tuple<tArgs...> &pArgs,const MIndexTuple<vIndex,vIndices...>&)
    {
        extract_value(this,std::get<vIndex>(pArgs));
        ExtractTupleImpl(pArgs,MIndexTuple<vIndices...>());
    }

public:
    template<typename... tArgs>
    void ExtractTuple(std::tuple<tArgs...> &pArgs)
    {
        typedef typename MMakeIndices<sizeof...(tArgs)>::type tIndices;
        ExtractTupleImpl(pArgs,tIndices());
    }

    //! starts appending an array
    void StartArray(const char *pS);
    //! finishes appending an array
    void EndArray();
    //! starts appending a dictionary
    void StartDictEntry();
    //! finishes appending a dictionary
    void EndDictEntry();
    //! starts appending a struct
    void StartStruct();
    //! finishes appending a struct
    void EndStruct();
    //! starts extracting container
    void OpenContainer();
    //! finishes extracting an array or a container
    void CloseContainer();
    
    //! extracts a basic dbus type
    void ExtractBasic(char pType,void *pValue);
    //! extracts a basic dbus type
    void AppendBasic(char pType,void *pValue);

    //! returns the service this message was received from
    std::string GetSender();
    //! returns the path this message was/will be sent to
    std::string GetPath();
    //! returns the member (or signal) invoked by this message
    std::string GetMember();
    //! returns the interface used by the message
    std::string GetInterface();
    //! returns true if this message represents an error
    bool IsError();

    //! gets a new CMessage which can be used to reply to this message
    PMessage BuildReply();
    //! gets a new CMessage which can be used to report an error in this message
    PMessage BuildError(const std::string &pName,
                           const std::string &pMsg=std::string());
    void Send();
    
    CBus *GetBus()  {   return mBus;    }
//@}

protected:
    std::unique_ptr<SPrivate> mP;
    CBus *mBus;
    uint32_t mExpectedSerial;
    
    SIterator *mIterator;
    
    friend class CBus;
};

void append_value(CMessage *pM,char pV);
void append_value(CMessage *pM,wchar_t pV);
void append_value(CMessage *pM,int8_t pV);
void append_value(CMessage *pM,uint8_t pV);
void append_value(CMessage *pM,int16_t pV);
void append_value(CMessage *pM,uint16_t pV);
void append_value(CMessage *pM,int32_t pV);
void append_value(CMessage *pM,uint32_t pV);
void append_value(CMessage *pM,int64_t pV);
void append_value(CMessage *pM,uint64_t pV);
void append_value(CMessage *pM,float pV);
void append_value(CMessage *pM,double pV);
void append_value(CMessage *pM,bool pV);
void append_value(CMessage *pM,const char *pV);
void append_value(CMessage *pM,const std::string &pV);
void append_value(CMessage *pM,const std::wstring &pV);
void append_value(CMessage *pM,const io::CURI &pV);
void append_value(CMessage *pM,const UValue &pV);
void append_value(CMessage *pM,const CObjectPath &pV);

template<typename tType>
inline void append_value(CMessage *pM,const std::vector<tType> &pV)
{
    pM->StartArray(MSignature<tType>::Get());
    for(auto i=pV.begin();i!=pV.end();++i)
        append_value(pM,*i);
    pM->EndArray();
}

template<typename t1,typename t2>
inline void append_value(CMessage *pM,const std::map<t1,t2> &pV)
{
    pM->StartArray((std::string("{")+MSignature<t1>::Get()+MSignature<t2>::Get()+"}").c_str());
    for(auto i=pV.begin();i!=pV.end();++i)
    {
        pM->StartDictEntry();
        append_value(pM,i->first);
        append_value(pM,i->second);
        pM->EndDictEntry();
    }
    pM->EndArray();
}

void extract_value(CMessage *pM,char &pV);
void extract_value(CMessage *pM,wchar_t &pV);
void extract_value(CMessage *pM,int8_t &pV);
void extract_value(CMessage *pM,uint8_t &pV);
void extract_value(CMessage *pM,int16_t &pV);
void extract_value(CMessage *pM,uint16_t &pV);
void extract_value(CMessage *pM,int32_t &pV);
void extract_value(CMessage *pM,uint32_t &pV);
void extract_value(CMessage *pM,int64_t &pV);
void extract_value(CMessage *pM,uint64_t &pV);
void extract_value(CMessage *pM,float &pV);
void extract_value(CMessage *pM,double &pV);
void extract_value(CMessage *pM,bool &pV);
void extract_value(CMessage *pM,std::string &pV);
void extract_value(CMessage *pM,std::wstring &pV);
void extract_value(CMessage *pM,io::CURI &pV);
void extract_value(CMessage *pM,UValue &pV);
void extract_value(CMessage *pM,CProxy &pV);

template<typename tType>
inline void extract_value(CMessage *pM,std::vector<tType> &pV)
{
    pV.clear();
    pM->OpenContainer();
    while(pM->CanExtract())
    {
        pV.emplace_back(pM->Extract<tType>());
    }
    pM->CloseContainer();
}

template<typename t1,typename t2>
inline void extract_value(CMessage *pM,std::map<t1,t2> &pV)
{
    pV.clear();
    pM->OpenContainer();
    while(pM->CanExtract())
    {
        t1 l1;
        t2 l2;
        extract_value(pM,l1);
        extract_value(pM,l2);
        pV.insert(std::make_pair(std::move(l1),std::move(l2)));
    }
    pM->CloseContainer();
}

void extract_value(CMessage *pM,CPath &pV);
void extract_value(CMessage *pM,CMember &pV);
void extract_value(CMessage *pM,CInterface &pV);

///@}

/*namespace dbus*/ } /*namespace buola*/ }

#endif
